﻿/*
This software is copyright 2009 Matthew Wilson matthew@wilson.org
and available under the Artistic License 2 ("AL2") as documented at
http://jsmeta.googlecode.com
*/

var Dom = {
  Get: function(el) {
    if (typeof el === 'string') {
      return document.getElementById(el);
    } else {
      return el;
    }
  },
  add: function(el, dest) {
    var el = this.Get(el);
    var dest = this.Get(dest);
    dest.appendChild(el);
  },
  remove: function(el) {
    var el = this.Get(el);
    el.parentNode.removeChild(el);
  },
  create: function(nodeType) {
    return document.createElement(nodeType);
  }
};

var Print, Error;

var sheet, Cols = 26, Rows = 30, colNum, cellDiv
  , rowNum, row, colHead, cells = {}, compContext;

function buildSheet() {
  var cell;
  sheet = Dom.Get('sheet');
  
  var thead = Dom.create('thead');
  var header = Dom.create('tr');
  var tbody = Dom.create('tbody');
  for (rowNum = 1; rowNum <= Rows; rowNum++) {
    row = cells[rowNum] = Dom.create('tr');
    row.className = rowNum;
    for (var i = 0; i <= Cols; i++) {
      colNum = String.fromCharCode('A'.charCodeAt(0) + i - 1);
      if (rowNum==1) {
        cells[colNum] = colHead = Dom.create('th');
        colHead.style.width = i ? "40px" : "20px";
        colHead.innerHTML = i ? colNum : '&nbsp;';
        header.appendChild(colHead);
      }
      if (!i) {
        cells[rowNum] = cell = Dom.create('td');
        cell.className = 'colBase'
        cell.innerHTML = rowNum;
        row.appendChild(cell);
      } else {
        cells[colNum] = cells[colNum] || {};
        cell = Dom.create('td');
        //cell.className = ''; //'col' + colNum + ' row' + rowNum;
        cell.appendChild(cell.div = cellDiv = Dom.create('div'));
        cellDiv.cell = cell;
        cell.colNum = colNum;
        cell.rowNum = rowNum;
        cell.lastValue = cell.lastFormula = cell.formula = '';
        cells[colNum][rowNum] = cells[cell.id = colNum + '' + rowNum] = cell;
        cellDiv.innerHTML = '&nbsp;';
        cell.onclick = cellClick;
        cell.ondblclick = cellDblClick;
        cell.deps = {}; // dependENTS
        cell.dependencies = {};
        cell.activator = 
        cell.activation = cellActivationGenerator(cell.id, cell);
        cell.unselectable = 'on';
        cellDiv.unselectable = 'on';
        row.appendChild(cell);
      }
    }
    tbody.appendChild(row);
  }
  thead.appendChild(header);
  sheet.appendChild(thead);
  sheet.appendChild(tbody);
  resizableTables[0] = new ColumnResize(sheet);
  dt.style.display = 'inline';
  dt.focus(); dt.blur();
  dt.style.display = 'none';
}

var highlighted = [];

function cellClick(e) {
  //Print('cellClick fired');
  e = e || event;
  var target = e.target || e.srcElement;
  if (typeof(edit)=='number') {
    if (typeof(target.div)=='undefined') {
      //Print('cellClick on div');
      return cellClick({ srcElement: target.cell });
    }
    //Print('cellClick on cell');
    target.focus();
    cellHighlight(target);
    if (target.onclick===cellClick) {
      /*@cc_on /* 
      target.div.ondblclick = target.ondblclick = noop; // @*/
      setTimeout(function() {
        target.onclick = cellClick;
        /*@cc_on /* 
        target.div.ondblclick = target.ondblclick = cellDblClick; // @*/
        //Print('reset secondClick event');
      }, 400);
      target.onclick = cellEdit;
    }
  }
  return false;
}

function cellDblClick(e) {
  //Print('cellDblClick fired');
  e = e || event;
  var target = e.target || e.srcElement;
  if (typeof(edit)=='number') {
    if (typeof(target.cell)!='undefined') {
      cellEdit({ target: target.cell});
      return false;
    }
    cellEdit(e);
  }
  return false;
}

function clearOutput() {
  Dom.Get('output').innerHTML = '';
}

function cellHighlight(cell) {
  //Print('cellHighlight fired');
  //Print('highlighting '+cell.id);
  cell.onkeydown = cellContainerKeyDown;
  for (var i = 0, len=highlighted.length; i < len; i++) {
    highlighted[i].className = '';
    cells[highlighted[i].rowNum].className = 'colBase';
    cells[highlighted[i].colNum].className = '';
  }
  cell.className = 'highlighted';
  cells[cell.rowNum].className = 'current';
  cells[cell.colNum].className = 'current';
  highlighted.push(cell);
  highlight = cell;
  if (!cell.hasFocus) {
    cell.focus();
  }
  return true;
}

function moveLeft(cell) {
  //Print('moveLeft fired');
  var newCell = cells[String.fromCharCode(cell.colNum.charCodeAt(0)-1)][cell.rowNum];
  newCell.focus();
  cellHighlight(newCell);
}

function moveLeftHome(cell) {
  //Print('moveLeftHome fired');
  var newCell = cells['A'][cell.rowNum];
  newCell.focus();
  cellHighlight(newCell);
}

function moveRightHome(cell) {
  //Print('moveRightHome fired');
  var newCell = cells[String.fromCharCode('A'.charCodeAt(0)+Cols-1)][cell.rowNum];
  newCell.focus();
  cellHighlight(newCell);
}

function moveRight(cell) {
  //Print('moveRight fired');
  var newCell = cells[String.fromCharCode(cell.colNum.charCodeAt(0)+1)][cell.rowNum];
  newCell.focus();
  cellHighlight(newCell);
}

function moveDown(cell) {
  //Print('moveDown fired');
  var newCell = cells[cell.colNum][cell.rowNum+1];
  newCell.focus();
  cellHighlight(newCell);
}

function moveUp(cell) {
  //Print('moveUp fired');
  var newCell = cells[cell.colNum][cell.rowNum-1];
  newCell.focus();
  cellHighlight(newCell);
}

function cellContainerKeyDown(e) {
  //Print('cellContainerKeyDown fired');
  e = e || event;
  var key = KeyCode.translate_event(e);
  var target = e.overrideTarget || e.target || e.srcElement;
  if (typeof(target.div)!='undefined') {
    if (key.code==113) { // F2 key
      cellEdit({ srcElement: target });
      return false;
    } else if (key.code==13 || key.code==40) { // enter key or arrow down
      moveDown(target);
      return false;
    } else if (key.code==37) { // arrow left
      moveLeft(target);
      return false;
    } else if (key.code==39) { // arrow right
      moveRight(target);
      return false;
    } else if (key.code==38) { // arrow up
      moveUp(target);
      return false;
    } else if (key.code==36) { // home key
      moveLeftHome(target);
      return false;
    } else if (key.code==35) { // end key (don't emulate "end mode" yet)
      moveRightHome(target);
      return false;
    } else if (key.code==46) { // delete key
      target.formula = '';
      text(target.div, '&nbsp;');
      cellEdit({ target: target });
      target.focus();
      return false;
    } else if (key.code==9) { // tab key
      if (key.shift) {
        moveLeft(target);
      } else {
        moveRight(target);
      }
      return false;
    } else if (key.code==8) { // backspace
      cellEdit({ srcElement: target }, '');
    } else if (key.code==27 || key.code==16 || key.code==17 || key.code==18) {
      return false;
    } else {
      cellEdit({ srcElement: target }, document.all ? '' : KeyCode.to_ascii(key));
      return true;
    }
  } else {
    return false;
  }
}

function snagKeyDown(e) {
  //Print('snagKeyDown fired');
  e = e || event;
  if (typeof(edit)=='number') {
    if (highlight) {
      e.overrideTarget = highlight;
      cellContainerKeyDown(e);
    }
    if (e.stopPropagation) {
      e.stopPropagation();
    } else {
      e.cancelBubble = true;
    }
    if (e.preventDefault) {
      e.preventDefault();
    }
    return false;
  } else {
    //Print('editing '+edit.id);
  }
  return true;
}

function divKeyDown(e) {
  //Print('divKeyDown fired');
  e = e || event;
  var myEdit = edit;
  if (typeof(myEdit)!='number') {
    //myEdit.lastPos = getCaretPos(myEdit);
    if (e.keyCode==13) { // enter key
      myEdit.div.blur();
      myEdit.div.contentEditable = false;
      setTimeout(function() {
        dt.style.display = 'inline';
        dt.focus(); dt.blur();
        dt.style.display = 'none';
        //myEdit.div.contentEditable = true;
        if (typeof(cells[myEdit.colNum][myEdit.rowNum+1])!='undefined') {
          moveDown(myEdit);
          //cells[myEdit.colNum][myEdit.rowNum+1].div.focus();
        }
      }, 0);
    } else if (e.keyCode==27) { // escape key
      //Print('canceling edit of '+myEdit.id+'; reverting formula to '+myEdit.formula.toString().toProgramString()+' and lastValue to '+myEdit.lastValue.toString().toProgramString());
      text(myEdit.div, myEdit.lastValue);
      myEdit.div.onblur = noop;
      myEdit.div.contentEditable = false;
      myEdit.div.unselectable = 'on';
      myEdit.className = '';
      edit = 0;
      myEdit.onclick = cellClick;
      myEdit.ondblclick = cellDblClick;
      setTimeout(function() {
        dt.style.display = 'inline';
        dt.focus(); dt.blur();
        dt.style.display = 'none';
        cellHighlight(myEdit);
      }, 0);
      //if (!myEdit.hasFocus) {
      //dt.focus(); //dt.blur();
      //myEdit.div.focus();
      //myEdit.focus();
      //}
      //cellHighlight(myEdit);
    } else {
      return true;
    }
    return false;
  }
  return true;
}

function text(obj, newVal) {
  return typeof(newVal)!='undefined'
    ? (obj.firstChild.nodeValue = newVal)
    : obj.firstChild.nodeValue;
}

var noop = function(){};
var edit = 0, highlight = 0;
var cellsUpdatingByGen = [];
var rootCellByGen = [];

function cellEdit(e, initialValue) {
  var target, e = e ? e : window.event;
  if (!e.button) {
    target = (e.target || e.srcElement);
    if (target !==null && typeof(target.div)!='undefined') {
      var myEdit, div;
      text((div = (myEdit = edit = target).div),
        typeof(initialValue)!='undefined'
          ? initialValue
          : target.formula);
      div.onblur = function() {
        //Print('unsetting onblur for '+myEdit.id);
        div.onblur = noop;
        div.contentEditable = false;
        div.unselectable = 'on';
        myEdit.className = '';
        edit = 0;
        myEdit.formula = text(div);
        //Print({ formula: myEdit.formula, lastFormula: myEdit.lastFormula });
        if (myEdit.formula!=myEdit.lastFormula) {
          gen++;
          computeContext.idx++;
          Print('cellEdit calling reparse');
          var newVal = myEdit.lastValue = reparse(myEdit)+''; // stringify
          Print('cellEdit reparse returned: '+newVal);
          if (newVal == '') {
            div.innerHTML = '&nbsp;';
          } else if (newVal == 'NaN') {
            div.innerHTML = '#!Value';
          } else {
            div.innerHTML = newVal;
          }
        } else {
          Print('no formula changes: '+myEdit.formula.toString().toProgramString());
          text(div, myEdit.lastValue);
        }
        myEdit.onclick = cellClick;
        myEdit.ondblclick = cellDblClick;
        if (!myEdit.hasFocus) {
          myEdit.focus();
        }
        return false;
      };
      //edit.lastPos = getCaretPos(edit);
      //Print('editing '+target.id);
      myEdit.ondblclick = myEdit.onclick = myEdit.div.onclick = myEdit.div.ondblclick = myEdit.onkeydown = noop;
      myEdit.div.contentEditable = true;
      myEdit.div.onkeydown = divKeyDown;
      myEdit.className = 'editing';
      myEdit.div.unselectable = null;
      if (!myEdit.div.hasFocus) {
        myEdit.div.focus();
      }
      //setCaretPosToEnd(myEdit.div);
      return true;
    }
  }
  return true;
}

function reparse(cell) {
  cell.activation.phase = 0;
  return cell.activation.run(computeContext);
}


// written by Dean Edwards, 2005
// with input from Tino Zijdel - crisp@xs4all.nl
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler)
{
	if (element.addEventListener)
		element.addEventListener(type, handler, false);
	else
	{
		if (!handler.DeAnEdWaRdSguid) handler.DeAnEdWaRdSguid = addEvent.guid++;
		if (!element.events) element.events = {};
		var handlers = element.events[type];
		if (!handlers)
		{
			handlers = element.events[type] = {};
			if (element['on' + type]) handlers[0] = element['on' + type];
			element['on' + type] = handleEvent;
		}
	
		handlers[handler.DeAnEdWaRdSguid] = handler;
	}
}
addEvent.guid = 1;

function removeEvent(element, type, handler)
{
	if (element.removeEventListener)
		element.removeEventListener(type, handler, false);
	else if (element.events && element.events[type] && handler.DeAnEdWaRdSguid)
		delete element.events[type][handler.DeAnEdWaRdSguid];
}

function handleEvent(event)
{
	event = event || fixEvent(window.event);
	var returnValue = true;
	var handlers = this.events[event.type];

	for (var i in handlers)
	{
		if (!Object.prototype[i])
		{
			this.DeAnEdWaRdShandler = handlers[i];
			if (this.DeAnEdWaRdShandler(event) === false) returnValue = false;
		}
	}

	if (this.DeAnEdWaRdShandler) this.DeAnEdWaRdShandler = null;

	return returnValue;
}

function fixEvent(event)
{
	event.preventDefault = fixEvent.preventDefault;
	event.stopPropagation = fixEvent.stopPropagation;
	return event;
}
fixEvent.preventDefault = function()
{
	this.returnValue = false;
}
fixEvent.stopPropagation = function()
{
	this.cancelBubble = true;
}

// This little snippet fixes the problem that the onload attribute on the body-element will overwrite
// previous attached events on the window object for the onload event
if (!window.addEventListener)
{
	document.onreadystatechange = function()
	{
		if (window.onload && window.onload != handleEvent)
		{
			addEvent(window, 'load', window.onload);
			window.onload = handleEvent;
		}
	}
}

addEvent(window, 'load', function() {
  if (!debugMode) {
    if (Dom.Get('fullViewRow1')) {
      Dom.Get('fullViewRow1').deleteCell(0);
    }
  }
  formulaParser = /*(function() {
    return*/ Module.Exec(
      function runit(l) {
        l.__Import('JSMetaSheet');
        parseContext = l; // initialize formula parsing context
        return l.JSMetaSheet.Formula.Main();
      }
    );
  /*})();*/
  Module.Exec(function runit(l) {
    return computeContext = l; // initialize formula expression computation context
  });
  
  computeContext.__Init('');
  
  // specialize the Print function depending on debug mode
  Print = (function() {
    if (typeof(debugMode)!='undefined' && debugMode) {
      var out = Dom.Get('output');
      return function(outputText) {
        out.innerHTML += outputText.toString() + '<br />';
        out.scrollTop = out.scrollHeight;
      };
    } else {
      return function(){};
    }
  })();
  Error = (function() {
    if (typeof(debugMode)!='undefined' && debugMode) {
      return function(outputText) {
        Print(outputText);
        throw outputText;
      };
    } else {
      return function(outputText) {
        alert(outputText);
        throw outputText;
      }
    }
  })();
  //document.body.onmousedown = Unsel.enable;
  //document.body.onmouseup = Unsel.disable;
  dt = Dom.Get('dt');
  dt.onkeyup = function() { return false; };
  setTimeout(buildSheet, 0);
  if (!document.all) {
    document.onkeydown = snagKeyDown;
  }
});

var dt;

